# Copyright 2018 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//examples/wheel/private:wheel_utils.bzl", "directory_writer", "make_variable_tags")
load("//python:defs.bzl", "py_library", "py_test")
load("//python:packaging.bzl", "py_package", "py_wheel")
load("//python:pip.bzl", "compile_pip_requirements")
load("//python:versions.bzl", "gen_python_config_settings")
load("//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")  # buildifier: disable=bzl-visibility

package(default_visibility = ["//visibility:public"])

licenses(["notice"])  # Apache 2.0

py_library(
    name = "main",
    srcs = ["main.py"],
    deps = [
        "//examples/wheel/lib:simple_module",
        "//examples/wheel/lib:module_with_data",
        # Example dependency which is not packaged in the wheel
        # due to "packages" filter on py_package rule.
        "//tests/load_from_macro:foo",
    ],
)

py_library(
    name = "main_with_gen_data",
    srcs = ["main.py"],
    data = [
        ":gen_dir",
    ],
)

directory_writer(
    name = "gen_dir",
    out = "someDir",
    files = {"foo.py": ""},
)

# Package just a specific py_libraries, without their dependencies
py_wheel(
    name = "minimal_with_py_library",
    testonly = True,  # Set this to verify the generated .dist target doesn't break things
    # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
    distribution = "example_minimal_library",
    python_tag = "py3",
    # NOTE: twine_binary = "//tools/publish:twine" does not work on non-bzlmod
    # setups because the `//tools/publish:twine` produces multiple files and is
    # unsuitable as the `src` to the underlying native_binary rule.
    twine = None if BZLMOD_ENABLED else "@rules_python_publish_deps_twine//:pkg",
    version = "0.0.1",
    deps = [
        "//examples/wheel/lib:module_with_data",
        "//examples/wheel/lib:simple_module",
    ],
)

# Populate a rule with "Make Variable" arguments for
# abi, python_tag and version. You might want to do this
# for the following use cases:
#  - abi, python_tag: introspect a toolchain to map to appropriate cpython tags
#  - version: populate given this or a dependent module's version
make_variable_tags(
    name = "make_variable_tags",
)

py_wheel(
    name = "minimal_with_py_library_with_make_variables",
    testonly = True,
    abi = "$(ABI)",
    distribution = "example_minimal_library",
    python_tag = "$(PYTHON_TAG)",
    toolchains = ["//examples/wheel:make_variable_tags"],
    version = "$(VERSION)",
    deps = [
        "//examples/wheel/lib:module_with_data",
        "//examples/wheel/lib:simple_module",
    ],
)

build_test(
    name = "dist_build_tests",
    targets = [":minimal_with_py_library.dist"],
)

# Package just a specific py_libraries, without their dependencies
py_wheel(
    name = "minimal_with_py_library_with_stamp",
    # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
    distribution = "example_minimal_library{BUILD_USER}",
    python_tag = "py3",
    stamp = 1,
    version = "0.1.{BUILD_TIMESTAMP}",
    deps = [
        "//examples/wheel/lib:module_with_data",
        "//examples/wheel/lib:simple_module",
    ],
)

# Use py_package to collect all transitive dependencies of a target,
# selecting just the files within a specific python package.
py_package(
    name = "example_pkg",
    # Only include these Python packages.
    packages = ["examples.wheel"],
    deps = [":main"],
)

py_package(
    name = "example_pkg_with_data",
    packages = ["examples.wheel"],
    deps = [":main_with_gen_data"],
)

py_wheel(
    name = "minimal_with_py_package",
    # Package data. We're building "example_minimal_package-0.0.1-py3-none-any.whl"
    distribution = "example_minimal_package",
    python_tag = "py3",
    version = "0.0.1",
    deps = [":example_pkg"],
)

# An example that uses all features provided by py_wheel.
py_wheel(
    name = "customized",
    author = "Example Author with non-ascii characters: żółw",
    author_email = "example@example.com",
    classifiers = [
        "License :: OSI Approved :: Apache Software License",
        "Intended Audience :: Developers",
    ],
    console_scripts = {
        "customized_wheel": "examples.wheel.main:main",
    },
    description_file = "README.md",
    # Package data. We're building "example_customized-0.0.1-py3-none-any.whl"
    distribution = "example_customized",
    entry_points = {
        "console_scripts": ["another = foo.bar:baz"],
        "group2": [
            "second = second.main:s",
            "first = first.main:f",
        ],
    },
    extra_distinfo_files = {
        "//examples/wheel:NOTICE": "NOTICE",
        # Rename the file when packaging to show we can.
        "//examples/wheel:README.md": "README",
    },
    homepage = "www.example.com",
    license = "Apache 2.0",
    project_urls = {
        "Bug Tracker": "www.example.com/issues",
        "Documentation": "www.example.com/docs",
    },
    python_tag = "py3",
    # Requirements embedded into the wheel metadata.
    requires = ["pytest"],
    summary = "A one-line summary of this test package",
    version = "0.0.1",
    deps = [":example_pkg"],
)

# An example of how to change the wheel package root directory using 'strip_path_prefixes'.
py_wheel(
    name = "custom_package_root",
    # Package data. We're building "examples_custom_package_root-0.0.1-py3-none-any.whl"
    distribution = "examples_custom_package_root",
    entry_points = {
        "console_scripts": ["main = foo.bar:baz"],
    },
    python_tag = "py3",
    strip_path_prefixes = [
        "examples",
    ],
    version = "0.0.1",
    deps = [
        ":example_pkg",
    ],
)

py_wheel(
    name = "custom_package_root_multi_prefix",
    # Package data. We're building "custom_custom_package_root_multi_prefix-0.0.1-py3-none-any.whl"
    distribution = "example_custom_package_root_multi_prefix",
    python_tag = "py3",
    strip_path_prefixes = [
        "examples/wheel/lib",
        "examples/wheel",
    ],
    version = "0.0.1",
    deps = [
        ":example_pkg",
    ],
)

py_wheel(
    name = "custom_package_root_multi_prefix_reverse_order",
    # Package data. We're building "custom_custom_package_root_multi_prefix_reverse_order-0.0.1-py3-none-any.whl"
    distribution = "example_custom_package_root_multi_prefix_reverse_order",
    python_tag = "py3",
    strip_path_prefixes = [
        "examples/wheel",
        "examples/wheel/lib",  # this is not effective, because the first prefix takes priority
    ],
    version = "0.0.1",
    deps = [
        ":example_pkg",
    ],
)

py_wheel(
    name = "python_requires_in_a_package",
    distribution = "example_python_requires_in_a_package",
    python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
    python_tag = "py3",
    version = "0.0.1",
    deps = [
        ":example_pkg",
    ],
)

py_wheel(
    name = "use_rule_with_dir_in_outs",
    distribution = "use_rule_with_dir_in_outs",
    python_tag = "py3",
    version = "0.0.1",
    deps = [
        ":example_pkg_with_data",
    ],
)

gen_python_config_settings()

py_wheel(
    name = "python_abi3_binary_wheel",
    abi = "abi3",
    distribution = "example_python_abi3_binary_wheel",
    # these platform strings must line up with test_python_abi3_binary_wheel() in wheel_test.py
    platform = select({
        ":aarch64-apple-darwin": "macosx_11_0_arm64",
        ":aarch64-unknown-linux-gnu": "manylinux2014_aarch64",
        ":x86_64-apple-darwin": "macosx_11_0_x86_64",  # this is typically macosx_10_9_x86_64?
        ":x86_64-pc-windows-msvc": "win_amd64",
        ":x86_64-unknown-linux-gnu": "manylinux2014_x86_64",
    }),
    python_requires = ">=3.8",
    python_tag = "cp38",
    version = "0.0.1",
)

py_wheel(
    name = "filename_escaping",
    # Per https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
    # runs of "-", "_" and "." should be replaced with a single underscore.
    # Unicode non-ascii letters aren't allowed according to
    # https://packaging.python.org/en/latest/specifications/name-normalization/.
    distribution = "File--Name-Escaping",
    python_tag = "py3",
    version = "v0.0.1.RC1+ubuntu-r7",
    deps = [":example_pkg"],
)

write_file(
    name = "requires_file",
    out = "requires.txt",
    content = """\
# Requirements file
--index-url https://pypi.com

tomli>=2.0.0
starlark  # Example comment
""".splitlines(),
)

write_file(
    name = "extra_requires_file",
    out = "extra_requires.txt",
    content = """\
# Extras Requirements file
--index-url https://pypi.com

pyyaml>=6.0.0,!=6.0.1
toml; (python_version == "3.11" or python_version == "3.12") and python_version != "3.8"
wheel; python_version == "3.11" or python_version == "3.12"  # Example comment
""".splitlines(),
)

# py_wheel can use text files to specify their requirements. This
# can be convenient for users of `compile_pip_requirements` who have
# granular `requirements.in` files per package. This target shows
# how to provide this file.
py_wheel(
    name = "requires_files",
    distribution = "requires_files",
    extra_requires_files = {":extra_requires.txt": "example"},
    python_tag = "py3",
    # py_wheel can use text files to specify their requirements. This
    # can be convenient for users of `compile_pip_requirements` who have
    # granular `requirements.in` files per package.
    requires_file = ":requires.txt",
    version = "0.0.1",
    deps = [":example_pkg"],
)

# Package just a specific py_libraries, without their dependencies
py_wheel(
    name = "minimal_data_files",
    testonly = True,  # Set this to verify the generated .dist target doesn't break things

    # Re-using some files already checked into the repo.
    data_files = {
        "//examples/wheel:NOTICE": "scripts/NOTICE",
        "README.md": "data/target/path/README.md",
    },
    distribution = "minimal_data_files",
    version = "0.0.1",
)

py_wheel(
    name = "extra_requires",
    distribution = "extra_requires",
    extra_requires = {"example": [
        "pyyaml>=6.0.0,!=6.0.1",
        'toml; (python_version == "3.11" or python_version == "3.12") and python_version != "3.8"',
        'wheel; python_version == "3.11" or python_version == "3.12" ',
    ]},
    python_tag = "py3",
    # py_wheel can use text files to specify their requirements. This
    # can be convenient for users of `compile_pip_requirements` who have
    # granular `requirements.in` files per package.
    requires = [
        "tomli>=2.0.0",
        "starlark",
        'pytest; python_version != "3.8"',
    ],
    version = "0.0.1",
    deps = [":example_pkg"],
)

py_test(
    name = "wheel_test",
    srcs = ["wheel_test.py"],
    data = [
        ":custom_package_root",
        ":custom_package_root_multi_prefix",
        ":custom_package_root_multi_prefix_reverse_order",
        ":customized",
        ":extra_requires",
        ":filename_escaping",
        ":minimal_data_files",
        ":minimal_with_py_library",
        ":minimal_with_py_library_with_stamp",
        ":minimal_with_py_package",
        ":python_abi3_binary_wheel",
        ":python_requires_in_a_package",
        ":requires_files",
        ":use_rule_with_dir_in_outs",
    ],
    deps = [
        "//python/runfiles",
    ],
)

# Test wheel publishing

compile_pip_requirements(
    name = "requirements_server",
    src = "requirements_server.in",
)

py_test(
    name = "test_publish",
    srcs = ["test_publish.py"],
    data = [
        ":minimal_with_py_library",
        ":minimal_with_py_library.publish",
        ":pypiserver",
    ],
    env = {
        "PUBLISH_PATH": "$(location :minimal_with_py_library.publish)",
        "SERVER_PATH": "$(location :pypiserver)",
        "WHEEL_PATH": "$(rootpath :minimal_with_py_library)",
    },
    target_compatible_with = select({
        "@platforms//os:linux": [],
        "@platforms//os:macos": [],
        "//conditions:default": ["@platforms//:incompatible"],
    }),
    deps = [
        "@pypiserver//pypiserver",
    ],
)

py_console_script_binary(
    name = "pypiserver",
    pkg = "@pypiserver//pypiserver",
    script = "pypi-server",
)
